package edu.pku.sei.sct.evaluation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;

import edu.pku.sei.AMT.AmtPackage;
import edu.pku.sei.AMT.ObjectVariable;
import edu.pku.sei.AMT.Rule;
import edu.pku.sei.AMT.Variable;
import edu.pku.sei.ocl.cst.HelperCallExpCS;
import edu.pku.sei.ocl.cst.HelperDefCS;
import edu.pku.sei.ocl.cst.OCLExpressionCS;
import edu.pku.sei.ocl.cst.SimpleNameCS;
import edu.pku.sei.ocl.cst.VariableCS;
import edu.pku.sei.ocl.evaluation.Context;
import edu.pku.sei.ocl.evaluation.Environment;
import edu.pku.sei.ocl.evaluation.OCLVariable;
import edu.pku.sei.ocl.types.TypesPackage;
import edu.pku.sei.ocl.utilities.CacheUtil;
import edu.pku.sei.ocl.utilities.OCLConstant;
import edu.pku.sei.ocl.utilities.OCLStandardLibrary;
import edu.pku.sei.ocl.visitor.AbstractOCLVisitor;
import edu.pku.sei.sct.match.ModelManager;
import edu.pku.sei.sct.primitives.AbstractPrimitive;
import edu.pku.sei.sct.primitives.CreateElement;
import edu.pku.sei.sct.primitives.CreateRuleContext;
import edu.pku.sei.sct.primitives.DeleteElement;
import edu.pku.sei.sct.primitives.EnforceLink;
import edu.pku.sei.sct.primitives.EvaluateExpression;
import edu.pku.sei.sct.primitives.GetKeyExpressions;
import edu.pku.sei.sct.primitives.IsContextValid;
import edu.pku.sei.sct.primitives.IsModelFixed;
import edu.pku.sei.sct.primitives.MatchPattern;
import edu.pku.sei.sct.primitives.MatchVariable;
import edu.pku.sei.sct.primitives.Print;
import edu.pku.sei.sct.primitives.RemoveContext;
import edu.pku.sei.sct.primitives.RemoveLink;
import edu.pku.sei.sct.primitives.ResolveContext;
import edu.pku.sei.sct.primitives.Schedule;
import edu.pku.sei.sct.primitives.SortVariables;
import edu.pku.sei.sct.primitives.StoreContext;

public class SCTEnvironment extends Environment {
	public ModelManager manager = new ModelManager();
	private CacheUtil typeCache = new CacheUtil();
	private CacheUtil refCache = new CacheUtil();
	
	private List<AbstractPrimitive> primitives = new ArrayList<AbstractPrimitive>();
	{
		primitives.add(new MatchPattern(this));
		primitives.add(new EvaluateExpression(this));
		primitives.add(new MatchVariable(this));
		primitives.add(new GetKeyExpressions(this));
		primitives.add(new SortVariables(this));
		primitives.add(new CreateElement(this));
		primitives.add(new DeleteElement(this));
		primitives.add(new EnforceLink(this));
		primitives.add(new RemoveLink(this));
		primitives.add(new CreateRuleContext(this));
		primitives.add(new IsModelFixed(this));
		primitives.add(new StoreContext(this));
		primitives.add(new RemoveContext(this));
		primitives.add(new IsContextValid(this));
		primitives.add(new ResolveContext(this));
		primitives.add(new Schedule(this));
		primitives.add(new Print(this));
	}
	
	public void initModelManager() {
		manager.setTypeMap(transformationEnvironment.name2types);
		for(Entry<String, EPackage> e : transformationEnvironment.name2types) {
			manager.initContainer(e.getKey(),transformationEnvironment.name2models.get(e.getKey()));
		}
	}
	
	public AbstractPrimitive getPrimitive(String name) {
		for(AbstractPrimitive p : primitives) {
			if(p.getID().equals(name))
				return p;
		}
		return null;
	}

	public SCTEnvironment() {
		super();
		loadMetamodel(AmtPackage.eINSTANCE);
		modeTree = new ModeTree();
	}
	
	private void loadMetamodel(AmtPackage einstance) {
		this.name2types.put(OCLConstant.DEFAULTMODEL, einstance);
	}

	private TransformationEnvironment transformationEnvironment;
	
	public TransformationEnvironment getTransformationEnvironment() {
		return transformationEnvironment;
	}
	public void setTransformationEnvironment(TransformationEnvironment transformationEnvironment) {
		this.transformationEnvironment = transformationEnvironment;
		transformationEnvironment.setSctEnvironment(this);
	}
	
	public EClass getType(String scope, String name) {
		EClass c = (EClass) typeCache.get(scope,name);
		if(c==null) {
			Object[] r  = transformationEnvironment.searchType(new String[]{scope,name});
			if(r==null)
				assert false;
			c = (EClass) r[1];
			typeCache.put(c, scope,name);
		}
		
		return c;
	}
	
	public EReference getReference(String scope, String type, String ref) {
		EReference r = (EReference) refCache.get(scope,type,ref);
		
		if(r==null) {
			EClass c = getType(scope,type);
			for(EReference x : c.getEAllReferences()) {
				if(x.getName().equals(ref)) {
					r = x;
					break;
				}
			}
			if(r==null) assert false;
			refCache.put(r, scope,type,ref);
		}
		return r;
	}

	public EClassifier getPrimitiveType(String type) {
		if("String".equals(type))
			return TypesPackage.eINSTANCE.getStringType();
		if("Integer".equals(type))
			return TypesPackage.eINSTANCE.getIntegerType();
		if("Real".equals(type))
			return TypesPackage.eINSTANCE.getRealType();
		if("Boolean".equals(type))
			return TypesPackage.eINSTANCE.getBooleanType();
		if("OclAny".equals(type))
			return TypesPackage.eINSTANCE.getOclAny();
		
		return  TypesPackage.eINSTANCE.getOclAny();
	}
	
	public void fillContext(Rule rule, Context initialContext) {
		for(Object o : rule.getVariables()) {
			if(o instanceof ObjectVariable) {
				String name = ((ObjectVariable) o).getName();
				String scope = ((ObjectVariable) o).getScope();
				String type = ((ObjectVariable) o).getType();
				EClass eType =getType(scope, type);
				OCLVariable var = new OCLVariable();
				var.setModelName(scope);
				var.setType(eType);
				var.setName(name);
				
				initialContext.registerVariable(var);
				
			} else {
				String name = ((Variable) o).getName();
				String type = ((Variable) o).getType();
				
				EClassifier eType = getPrimitiveType(type);
				OCLVariable var = new OCLVariable();
				var.setName(name);
				var.setType(eType);
				initialContext.registerVariable(var);
			}
		}
	}
	
	public Object invokeRuleEntry(String mode, Rule rule, Context initialContext, TransformationEnvironment en) {
		fillContext(rule,initialContext);
		 
		HelperDefCS def = this.getHelper("ExecuteRule");
		if(def!=null) {
			Object result = null;
			Context sctContext = createContext(this);
			EList<VariableCS> params = def.getParameters();
			//EList<OCLExpressionCS> arguments = callexp.getArguments();
			
			int i = 0;
			
			for(i=0;i<params.size();i++){
				VariableCS var = params.get(i);
				OCLVariable v = (OCLVariable) var.accept(AbstractOCLVisitor.modelCheck, sctContext);
				sctContext.registerVariable(v);
				switch(i) {
				case 0:
					sctContext.putValue(v, mode);
					break;
				case 1:
					sctContext.putValue(v, rule);
					break;
				case 2:
					sctContext.putValue(v, initialContext);
					break;
				case 3:
					sctContext.putValue(v, en);
					break;
				}
			}
			
			
			result = def.getBody().accept(AbstractOCLVisitor.modelCheck, sctContext);
			
			return result;
		}
		
		return null;
		
	}
	

	


	@Override
	protected Object invokeStaticHelper(String helperName,
			AbstractOCLVisitor visitor, Context context, Object... params) {
		try{
			return invokeStaticHelper(helperName, params);
		} catch (Exception e) {
			AbstractPrimitive p = this.getPrimitive(helperName);
			if(p!=null)
				return OCLStandardLibrary.wrap(p.execute(visitor, context, params));
			return OCLConstant.OCLInvalid;
		}
	}

	@Override
	protected Object invokeStaticHelper(HelperCallExpCS callexp,
			AbstractOCLVisitor visitor, Context context) {
		String helperName = callexp.getHelperName().getValue();
		//System.out.println("invoke helper "+helperName);
		EList<OCLExpressionCS> arguments = callexp.getArguments();
		Object[] actualParams = new Object[arguments.size()];
		for(int i=0;i<actualParams.length;i++) {
			actualParams[i] = arguments.get(i).accept(visitor, context);
		}
		
		try {
			if(callexp.isDefaultAM()==false)
				throw new Exception("when calling static helper, isDefaultAM must be true");
			return invokeStaticHelper(helperName, visitor, context, actualParams);
		} catch (Exception e) {
//			AbstractPrimitive p = this.getPrimitive(helperName);
//			if(p!=null)
//				return OCLStandardLibrary.wrap(p.execute(visitor, context, actualParams));
			return OCLConstant.OCLInvalid;
		}
	}
	
	private ModeTree modeTree;
	public void declareNewMode(String mode,String base) {
		modeTree.putMode(mode, base);
	}
	public String resolveMode(String mode){
		String expMode =  modeTree.getFinalMode(mode);
		
		try {
			if(expMode == null) {
				expMode = (String)this.invokeHelper("convertMode", AbstractOCLVisitor.expressionCheck, mode);
				declareNewMode(mode,expMode);
			}
		} catch (Exception e) {
			System.out.println("no base mode for "+mode+" can be found");
			expMode = "check";
		}
		
		return expMode;
	}
}

class ModeTree {
	public HashMap<String,String> tree = new HashMap<String,String>();
	
	public ModeTree() {
		putMode("check","check");
		putMode("enforce","enforce");
		putMode("propagation","propagation");
		putMode("imperative","imperative");
	}
	
	public void putMode(String mode,String base) {
		tree.put(mode, base);
	}
	public String getFinalMode(String m) {
		do {
			String tmp = tree.get(m);
			if(tmp==null)
				return null;
			if(tmp.equals(m))
				return m;
			m = tmp;
		}while(true);
	}
	
	
}

